﻿/******************************************************************************
 * Copyright (C) 671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "RealmdApplication.h"
#include <cstdio>
#include <csignal>
#include <NodeCpp/ThreadPool.h>
#include <NodeCpp/Time.h>
#include <NodeCpp/Util/Option.h>
#include <NodeCpp/Data/Rel/Details/MySQLConnector.h>
#include "Shared/Log/Log.h"
#include "GlobalVars.h"
#include "RealmList.h"
#include "Auth/AuthSocketDelegateFactory.h"
#include "Auth/AuthSessionManager.h"
using NodeCpp::ThreadPool;
using NodeCpp::Util::Option;
using NodeCpp::Data::Rel::MySQLConnector;

// 帧间隔时间(ms)
#define EVENT_CHECK_INTERVAL    10

IMPLEMENT_APPLICATION(Application, RealmdApplication)

RealmdApplication::RealmdApplication(void)
    : Application("Realmd")
    , ConfPath_("Realmd.conf")
    , Conf_()
    , LoginDB_()
    , NetServer_(new AuthSocketDelegateFactory)
    , Running_(false)
    , StopExit_()
{
    G_LoginDB_ = &LoginDB_;
}

RealmdApplication::~RealmdApplication(void)
{

}

void RealmdApplication::OnDefineOptions(OptionSet& _Options)
{
    Application::OnDefineOptions(_Options);

    _Options.AddOption(Option("conf", "c", "config file path").HasArg(true).IsRequired(false));
}

int RealmdApplication::OnOption(const std::string& _Name, const std::string& _Arg)
{
    if (_Name == "conf") {
        ConfPath_ = _Arg;
    }
    return Application::OnOption(_Name, _Arg);
}

int RealmdApplication::OnInitialize(void)
{
    /* 读取配置 */
    if (!Conf_.Open(ConfPath_)) {
        perror("Open config file");
        return EXIT_CONFIG;
    }

    /* 设置变量 */
    const struct Configuration::Any& _RealmdConf = Conf_["Realmd"];
    sAuthSessionMgr.SetMaxUnauthedTime((time_t)_RealmdConf["MaxUnauthedTime"].As<std::uint32_t>());
    sRealmList.SetRefreshInterval((time_t)_RealmdConf["RealmListRefreshTime"].As<std::uint32_t>());

    /* 初始化日志 */
    if (!sLog.Initialize(Conf_)) {
        perror("Initialize log failed");
        return EXIT_LOG;
    }

    /* 输出欢迎信息 */
    sLog.outString("[Realmd %s]\n", REALMD_VER);
    sLog.outString("<Ctrl-C> to stop.\n"
        "  __  __      _  _  ___  ___  ___                         \n"
        " |  \\/  |__ _| \\| |/ __|/ _ \\/ __|        We Love      \n"
        " | |\\/| / _` | .` | (_ | (_) \\__ \\      Vanilla Wow    \n"
        " |_|  |_\\__,_|_|\\_|\\___|\\___/|___/                    \n"
        "                                        ____              \n"
        " For help and support please visit:    /_  /___ _ _ ___   \n"
        " Website: https://getmangos.eu          / // -_) '_/ _ \\ \n"
        "    Wiki: http://github.com/mangoswiki /___\\___|_| \\___/\n"
        );
    sLog.outString();

    /* 连接登录数据库 */
    if (!MySQLConnector::Register()) {
        sLog.outError("Register MySQL connector failed");
        return EXIT_FAILURE;
    }

    const struct Configuration::Any& _DBConf = Conf_["Data"]["LoginDB"];
    sLoginDB.SetErrorHandler(__OnLoginDBError);
    if (!sLoginDB.Open(MySQLConnector::MYSQL_KEY,
        int(_DBConf["QueryConn"].As<std::int8_t>()),
        int(_DBConf["AsyncConn"].As<std::int8_t>()),
        _DBConf["Host"].As<std::string>(),
        _DBConf["Port"].As<std::string>(),
        _DBConf["User"].As<std::string>(),
        _DBConf["Password"].As<std::string>(),
        _DBConf["Database"].As<std::string>())) {
        sLog.outError("Start login database failed");
        return EXIT_FAILURE;
    }
    sLog.outString("MySQL Server version: %s", sLoginDB.GetServerInfo().c_str());
    sLog.outString("MySQL Client version: %s", sLoginDB.GetClientInfo().c_str());
    sLog.outString();

    /* 启动网络服务器 */
    const struct Configuration::Any& _NetConf = Application::conf("Net");
    NetServer_.SetThreadPool(&(ThreadPool::defaultPool()));
    std::string _Host = _NetConf["Host"].As<std::string>();
    int _Port = int(_NetConf["Port"].As<std::int16_t>());
    if (!NetServer_.Start(
        _Host.c_str(),
        _Port,
        int(_NetConf["Backlog"].As<std::int16_t>()),
        int(_NetConf["Workers"].As<std::int16_t>()))) {
        sLog.outError("%d, %s", NetServer_.GetErrorCode(), 
            NetServer_.GetErrorString().c_str());
        return false;
    }
    sLog.outString("Server is listen on %s:%d", _Host.c_str(), _Port);

    Running_ = true;
    return EXIT_OK;
}

void RealmdApplication::OnSignal(int _Signal)
{
    switch(_Signal)
    {
    case SIGINT:
#if defined(PLATFORM_LINUX)
    case SIGTERM:
#endif
        sLog.outString("*** caught SIGINT/SIGTERM, shutdown server ***");

        Running_ = false;
        StopExit_.Set();
        break;
    }
}

int RealmdApplication::Main(std::vector<std::string>& _UnknownArgs)
{
    /* 注册信号处理程序 */
    HookSignal(SIGINT);
#if defined(PLATFORM_LINUX)
    HookSignal(SIGTERM);
#endif

    time_t _WaitTime = 0;
    time_t _TmStart = 0;
    time_t _TmUsed = 0;
    time_t _Diff = 0;
    for (; Running_;)
    {
        _TmStart = NodeCpp::FromStartup();
        ///////////////////////////////////////////////////////////////////////
        // 更新服务器列表
        sRealmList.Update(_Diff);
        // 更新会话
        sAuthSessionMgr.Update(_Diff);
        // 处理数据库异步查询结果
        sLoginDB.ProcessAsyncResultQueue();
        ///////////////////////////////////////////////////////////////////////
        _TmUsed = NodeCpp::FromStartup() - _TmStart;
        _WaitTime = NODECPP_MAX(0, EVENT_CHECK_INTERVAL - _TmUsed);

        StopExit_.Wait(long(_WaitTime));
        if (!Running_) {
            break;
        }

        _Diff = NodeCpp::FromStartup() - _TmStart;
    }

    /* 停止接收新连接 */
    NetServer_.CloseListener();

    /* 关闭所有连接 */
    sAuthSessionMgr.Close();

    /* 关闭网络服务器 */
    NetServer_.Stop();

    /* 关闭数据库 */
    LoginDB_.Close();

    /* 关闭线程池 */
    ThreadPool::defaultPool().Stop();
    ThreadPool::defaultPool().JoinAll();

    sLog.outString("*** Server is stopped ***");
    return EXIT_SUCCESS;
}

void RealmdApplication::OnUninitialize(void)
{
    MySQLConnector::Unregister();
}